# coding:utf-8
# 王一舟
# yaml_tools.py
import copy

#yaml参数化的方法都保存在这里
import yaml
import os,uuid
import pathlib
from core.globalContext import g_Context


#TODO 将单个yaml文件的数据转换成字典 - 舍弃
#读取单个yaml文件的方法 -- 舍弃
def read_Yaml(file_path):
    '''
    :param file_path:yaml文件的路径
    :return: 读取文件成功,返回读取的数据 , 读取文件失败返回False
    '''
    #TODO 1.1 调用读取全局配置的方法, 因为本方法read_yaml()是用来读取case的,所以肯定会被调用,在读取case前先顺便把全局配置变量的数据一起加到全局变量中

    try:
        config_yaml_path = pathlib.Path(__file__).parents[1].resolve() / 'yaml_files'/'config.yaml'
        load_config_yaml(config_yaml_path)#读取config.yaml全局配置变量

        case_info = []
        with open(file_path,"r",encoding="utf-8") as f:

            # case_data = yaml.full_load(f)#加载所有数据
            case_data = yaml.load(stream=f,Loader=yaml.FullLoader)

            case_info.append(case_data) #组成 [{yaml里面的数据}]
            return case_info

    except Exception as e:
        print(f"加载{file_path}的yaml文件错误:{e}")
        return False


def load_config_yaml(yaml_folder_path):
    '''
    用来读取全局配置变量,把里面的数据先存放到全局变量中以供核心执行器执行每个步骤的时候一起渲染,把这个方法放在read_Yaml()方法中一起读取
    :param yaml_folder_path: config.yaml配置文件所在的文件夹目录
    :return:如果文件路径不存在,返回Flase
    '''
    try:
        config_yaml_path = os.path.join(yaml_folder_path , 'config.yaml') #因为config.yaml一般是和yaml用例文件放在一个目录下的,可以直接拼接出config.yaml的完整路径,

        with open(config_yaml_path, "r", encoding="utf-8") as f:

            # config_data = yaml.full_load(f)  # 加载所有数据
            config_data = yaml.load(stream=f,Loader=yaml.FullLoader) #通过yaml去加载文件流,读取yaml文件中的所有数据 ,读出来是个字典格式

            print(f"读取的config.yaml配置数据内容:{config_data}")

            #判断读取的yaml配置文件是否为空
            if config_data :#如果yaml配置文件中有读到数据吗就写到全局变量中
                g_Context().update_dict(config_data) #因为config_data读取出来的是一个字典,所以用update_dict()方法写入

    except Exception as e: #如果路径不对报错
        print(f"传入的是:{yaml_folder_path},需要传入config.yaml所在文件夹路径,错误:{e}")
        return False



#加载满足条件的yaml文件名及数据
def load_yaml_files(yaml_folder_path):
    '''
    读取某个文件夹下所有满足规则的yaml文件(测试用例)
    规则:
    以用例编号(数字)_XXXX.yaml  格式出现的文件则认为是测试用例
    :param yaml_folder_path: 传入yaml文件所存放的文件夹目录
    :return: 返回满足条件的yaml文件列表及数据
    '''
    try:

        #TODO 1 调用读取全局配置的方法, 因为本方法是用来读取case的,所以肯定会被调用,在读取case前先顺便把全局配置变量的数据一起加到全局变量中
        load_config_yaml(yaml_folder_path)  # 读取config.yaml全局配置变量

        #TODO 2 获取满足条件的列表
        yaml_case_info = []  # 空列表存储所有的数据
        yaml_fileName_list = [(int(f.split("_")[0]), f) for f in os.listdir(yaml_folder_path) if f.endswith(".yaml") and f.split("_")[0].isdigit()]
        '''
        (f.split("_")[0],f) : 代表以元组接收结果,内容是(int(序号),符合条件的文件名), 最后yaml_case_list是一个[(1,文件名),(2,文件名),(3,文件名)]的格式 ,序号提取出来是为了后续排序用
        for f in os.listdir(yaml_folder_path)  :  os.listdir(目录路径) , 返回的是一个列表, 可以获取指定目录下的所有文件名
             - 然后遍历这个目录下的所有文件名
        if f.endswith(".yaml") and f.split("_")[0].isdigit() : 将文件名以"_"分隔获取返回的列表的第0个值(即_前面的序号),判断这个序号是不是数字,如果符合这2个条件就把这个文件名添加到空列表
        输出: [('01', '01_登录成功测试用例+MySQL.yaml'), ('02', 'T02_登录失败测试用例.yaml'), ('03', '03_加入购物车成功用例.yaml')]
        '''
        #TODO 3 根据列表中的二元元组的第一个值排序,然后只保留第二个值(即文件名)
        yaml_fileName_list.sort() #排序
        yaml_fileName_list = [f[-1] for f in yaml_fileName_list] #遍历排序后的列表,,以f[-1]切片取出第二个值(去掉前面排序的序号,只取yaml文件名),组成新的列表

        #TODO 4 加载每个文件的数据,然后append到 yaml_case_info = []
        for yaml_fileName in yaml_fileName_list:
            yaml_caseName_path = os.path.join(yaml_folder_path,yaml_fileName) #拼接每个符合条件的yaml用例文件名的完整路径

            with open(yaml_caseName_path,"r",encoding="utf-8") as f:

                case_data = yaml.load(stream=f,Loader=yaml.FullLoader) #读取yaml用例的数据
                yaml_case_info.append(case_data) #组成 [{yaml里面的数据}]

        return yaml_case_info #返回所有的测试用例数据[]

    except Exception as e: #如果路径不对报错
        print(f"传入的路径是:{yaml_folder_path},需要传入yaml用例所在文件夹路径,错误:{e}")
        return False



'''
把yaml的格式处理成规定格式:
'''
def yaml_case_parser(yaml_folder_path):
    '''
    核心执行器不仅可以执行yaml,还要能执行excel
    所以要统一一个规范的格式进行处理
    返回指定的格式
    :param yaml_folder_path: 传入yaml文件存放的文件夹路径
    :return: {
            "case_infos": case_infos,  #  所有的测试用例 []
            "case_names": case_names   #  所有测试用例对应的标题 []
            }
    '''
    #TODO 1 :对应固定的格式
    case_infos = [] #存放用例数据
    case_names = [] #存放用例名称

    #TODO 2 :调用对应的方法,拿到所有的yaml用例数据
    yaml_case_infos = load_yaml_files(yaml_folder_path)

    # TODO 3 :统一处理成一样的格式(后面excel也需要同样处理)
    for info in yaml_case_infos: #遍历yaml读取的用例数据
        #TODO 扩展 : 单个yaml文件实现DDT,首先获取ddts的数据,得到长度
        ddts = info.get('ddts',[]) #从用例数据中得到ddts的数据保存为变量,如果有就获取,没有就返回一个空列表
        #TODO 扩展 : 取到ddts数据后,需要删除ddts数据,避免后续执行时干扰核心执行器
        if len(ddts) > 0 : #判断ddts长度是否大于0,大于0就代表有数据,那么就从原有的用例数据中把ddts删除
            info.pop("ddts") #从用例数据字典中把ddts的内容删除
        if len(ddts) == 0: #如果ddts的长度是0.那就说明原来用例中并没有ddts
            #按照原有逻辑直接处理即可
            case_name = info.get("desc", uuid.uuid4().__str__())  # 获取用例名称
            # 因为要组成的规定格式是用例数据和用例名称,所以必须要有用例名称
            # 如果desc获取不到(用例里没写),就通过uuid自动生成一个序列补上
            info.update({"_case_name": case_name})  # 把用例名称写到每条用例中去
            case_infos.append(info)  # 所有的测试用例[]
            case_names.append(case_name)  # 所有的用例名称[]

        # TODO 扩展 : 进到下面就说明用例中存在ddts,遍历ddts中的数据,ddts中有多少个ddt就生成多少条用例数据,每个用例数据中都添加一条context的key存放ddt数据
        else:
            '''
            测试用例中没有ddts的列表时,直接读取数据直接用,当有ddts的时,不能直接使用,而是根据ddts有几组数据,生成几个用例(遍历ddts)
            规范:
            每一个用例,多了一个ddt_context的key,存放当前ddt的数据
            用例名称:当前用例名称 - ddt当中的desc字段数据   
            '''
            #根据ddts生成生成多组数据
            for ddt in ddts:
                new_info = copy.deepcopy(info) #先深copy当前读到的测试用例数据
                ddt_context = new_info.get("ddt_context",{}) #给当前的new_info新增一个ddt_context的key,获取不到就是一个空字典
                ddt.update(ddt_context)#以防万一,若用例中原本真就存在ddt_context的key,那么把原有的context内容与ddt内容合并
                #此时的ddt包含了ddt 和 原用例中存在的context的内容
                new_info.update({"ddt_context" : ddt})

                #用例的值和对应的标题
                #因为有ddt的存在, 那么 标题格式就要改为  "用例的desc - ddt的desc"
                case_name = f'{info.get("desc", uuid.uuid4().__str__()) }-{ddt.get("desc", uuid.uuid4().__str__())}'
                # 因为要组成的规定格式是用例数据和用例名称,所以必须要有用例名称
                # 如果desc获取不到(用例里没写),就通过uuid自动生成一个序列补上
                new_info.update({"_case_name": case_name})  # 把用例名称写到每条用例中去

                case_infos.append(new_info)  # 所有的测试用例[]
                case_names.append(case_name)  # 所有的用例名称[]



    data = {
        "case_infos": case_infos,  # 所有的测试用例 []
        "case_names": case_names  # 所有测试用例对应的标题 []
    }

    return data

# if __name__ == '__main__':
#     print(yaml_case_parser(pathlib.Path(__file__).parents[1].resolve() / 'yaml_files'))